﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BFlatCompile.Nodes
{
	public class Namespace
	{
		public string Name { get; private set; }
		public int NameIndex { get { return Variable.Table.GetId(this.Name); } }
		public string FullName { get; private set; }
		public int ID { get; set; }

		private List<Class> classes = new List<Class>();
		private List<Namespace> namespaces = new List<Namespace>();
		private Dictionary<string, object> members = new Dictionary<string, object>();
		public List<Class> Classes { get { return this.classes; } }
		public List<Namespace> Namespaces { get { return this.namespaces; } }
		public Token OriginToken { get; private set; }

		public Namespace(Token originToken, string name, string parent, IList<Namespace> namespaces, IList<Class> subclasses)
		{
			this.Name = name;
			Variable.Table.Register(name);
			this.FullName = parent == null ? name : (parent + "." + name);

			this.OriginToken = originToken;

			foreach (Namespace n in namespaces)
			{
				this.AddNamespace(n);
			}

			foreach (Class c in subclasses)
			{
				this.AddClass(c);
			}
		}

		public object GetMember(string name)
		{
			foreach (Namespace ns in this.namespaces) { if (ns.Name == name) return ns; }
			foreach (Class c in this.classes) { if (c.Name == name) return c; }
			return null;
		}

		public void AddNamespace(Namespace ns)
		{
			string name = ns.Name;
			if (this.members.ContainsKey(name))
			{
				// TODO: show both
				throw new ParserException("Duplicate namespace members with name: " + name, ns.OriginToken);
			}

			this.namespaces.Add(ns);
			this.members.Add(name, ns);
		}

		public void AddClass(Class c)
		{
			string name = c.Name;
			if (this.members.ContainsKey(name))
			{
				// TODO: show both
				throw new ParserException("Duplicate namespace members with name: " + name, c.OriginToken);
			}

			this.classes.Add(c);
			this.members.Add(name, c);
		}

		public static Namespace ParseWithNamespaces(Tokens tokens, string parent)
		{
			Token token = tokens.PopExpected("namespace");

			string root = tokens.PopIdentifier();

			Namespace rootns = new Namespace(token, root, parent, new Namespace[0], new Class[0]);
			Namespace leaf = rootns;
			while (tokens.PopIfPresent("."))
			{
				leaf = new Namespace(token, tokens.PopIdentifier(), leaf.FullName, new Namespace[0], new Class[0]);
				rootns.AddNamespace(leaf);
			}

			tokens.PopExpected("{");

			bool foundSomething = true;
			while (foundSomething)
			{
				Class c = ParseClassIfPresent(tokens, leaf.FullName);
				Namespace n = ParseNamespaceIfPresent(tokens, leaf.FullName);

				if (c != null)
				{
					leaf.AddClass(c);
				}

				if (n != null)
				{
					leaf.AddNamespace(n);
				}

				foundSomething = (c != null || n != null);
			}

			tokens.PopExpected("}");

			return rootns;
		}

		private static Class ParseClassIfPresent(Tokens tokens, string parent)
		{
			if (tokens.IsPresent("static", "abstract", "class") || tokens.IsPresent("abstract", "static", "class"))
			{
				throw new ParserException("Class cannot be both static and abstract", tokens.PeekToken());
			}

			if (tokens.Peek() == "class" || tokens.IsPresent("static", "class") || tokens.IsPresent("abstract", "class"))
			{
				return Class.Parse(tokens, parent);
			}

			return null;
		}

		public void Merge(Namespace ns)
		{
			Dictionary<string, object> a = this.GetMemberLookup();
			Dictionary<string, object> b = ns.GetMemberLookup();
			foreach (string key in b.Keys)
			{
				if (a.ContainsKey(key))
				{
					if (a[key] is Namespace && b[key] is Namespace)
					{
						((Namespace)a[key]).Merge((Namespace)b[key]);
					}
					else
					{
						throw new Exception("Name conflict");
					}
				}
				else
				{
					a[key] = b[key];
				}
			}

			List<Namespace> namespaces = new List<Namespace>();
			List<Class> classes = new List<Class>();
			foreach (object member in a.Values)
			{
				if (member is Class)
				{
					classes.Add((Class)member);
				}
				else // if member is Namespace
				{
					namespaces.Add((Namespace)member);
				}
			}

			this.classes = classes;
			this.namespaces = namespaces;
		}

		private Dictionary<string, object> GetMemberLookup()
		{
			Dictionary<string, object> members = new Dictionary<string, object>();
			foreach (Namespace ns in this.namespaces)
			{
				members[ns.Name] = ns;
			}

			foreach (Class c in this.classes)
			{
				members[c.Name] = c;
			}

			return members;
		}

		public static Namespace ParseNamespaceIfPresent(Tokens tokens, string parent)
		{
			if (tokens.Peek() == "namespace")
			{
				return Namespace.ParseWithNamespaces(tokens, parent);
			}

			return null;
		}

		public override string ToString()
		{
			return "<Namespace> " + this.FullName;
		}
	}
}
